package mail.fetch.m21cn;

import mail.entity.ProxyEntity;
import mail.exception.ContentMissedException;
import mail.exception.MailDetailException;
import mail.exception.ParseMailException;
import mail.util.PublicMethod;
import mail.util.RegexSearcher;
import mail.util.ResponseElement;
import mail.util.StatusCode;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.openqa.selenium.json.JsonException;

import java.io.IOException;
import java.net.CookieManager;
import java.net.InetSocketAddress;
import java.net.ProxySelector;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects;

import static com.han.http.util.Print.printlnErr;
import static com.han.http.util.Print.printlnInfo;

/**
 * @author: Hanjiafeng
 * @date: Created in 16:48 2019/3/13
 * @desc: 21CN邮箱收件箱
 */
public class Inbox11 {
    /**
     * 定义发送对象Builder
     */
    private HttpClient.Builder clientBuilder;
    /**
     * 定义发送对象
     */
    private HttpClient httpClient;
    /**
     * 定义请求Builder
     */
    private HttpRequest.Builder requestBuilder;
    /**
     * 定义cookie管理器
     */
    private CookieManager cookieManager;
    /**
     * 默认第一封邮件发送事件在半年内
     */
    private boolean isDuringHalfYear = true;

    /**
     * 使用CookieManager和ProxyEntity初始化收件类
     *
     * @param cookieManager cm
     * @param proxyEntity   pe
     */
    Inbox11(CookieManager cookieManager, ProxyEntity proxyEntity) {
        this.cookieManager = cookieManager;
        clientBuilder = HttpClient.newBuilder()
                .connectTimeout(Duration.ofMinutes(1))
                .cookieHandler(cookieManager)
                //禁止自动跳转
                .followRedirects(HttpClient.Redirect.NEVER);
        //如果代理不为null,设置代理
        if (!Objects.isNull(proxyEntity)) {
            clientBuilder.proxy(ProxySelector.of(new InetSocketAddress(proxyEntity.getIp(), proxyEntity.getPort())));
        }
        //设置通用参数
        httpClient = clientBuilder
                .build();
        //设置通用参数
        requestBuilder = HttpRequest.newBuilder()
                .timeout(Duration.ofMinutes(1))
                .setHeader("Accept-Encoding", "gzip, deflate, br")
                .setHeader("Accept-Language", "zh-CN,zh;q=0.9")
                .setHeader("DNT", "1")
                .setHeader("Upgrade-Insecure-Requests", "1")
                .setHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3664.3 Safari/537.36");
    }

    /**
     * 获取半年内的信用卡账单邮件
     *
     * @return 半年内的邮件列表
     */
    List<Mail21cnEntity> getMailListDuringHalfYear() {

        List<Mail21cnEntity> duringHalfYearMailList = new ArrayList<>();
        int currentPageIndex = 1;
        boolean isLastPage = false;
        while (isDuringHalfYear && !isLastPage) {
            try {
                String mailListPage = getMailListByPageIndex(currentPageIndex);
                List<Mail21cnEntity> tempList = parseMailListPage(mailListPage);
                isLastPage = isLastPage(mailListPage, currentPageIndex);
                duringHalfYearMailList.addAll(tempList);
                //如果最后一封邮件也在180天之内并且不是最后一页,页码+1,继续获取下一页的邮件,重复判断
                if (isDuringHalfYear && !isLastPage) {
                    currentPageIndex++;
                }
            } catch (ContentMissedException e) {
                e.printStackTrace();
            }
            try {
                //休眠3秒,避免被封
                Thread.sleep(1000 * 3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        return duringHalfYearMailList;
    }

    /**
     * 获取指定页码的邮件页面
     *
     * @param pageIndex 邮件页码
     * @return 邮件页面
     * @throws ContentMissedException 没有获取到内容
     */
    private String getMailListByPageIndex(int pageIndex) throws ContentMissedException {
        String[] headers = {
                "Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
                "Content-Type", "application/x-www-form-urlencoded; charset=UTF-8"
        };
        String postData = "noCache=0.8504282437693056" +
                "&labelId=1" +
                "&pageNum=" + pageIndex +
                "&sortWay=68" +
                "&sortField=0";
        String uri = "http://mail.21cn.com/w2/mail/listMail.do";
        try {
            return PublicMethod.getResponseElement(httpClient, requestBuilder, uri, postData, ResponseElement.BODY, headers);
        } catch (IOException | InterruptedException e) {
            throw new ContentMissedException("没有获取到邮件列表:" + e.getMessage());
        }
    }

    /**
     * 检测当前页面是不是最后一页
     *
     * @param mailListPage     邮件页面
     * @param currentPageIndex 当前页码
     * @return 是否是最后一页
     */
    private boolean isLastPage(String mailListPage, int currentPageIndex) {
        boolean isLastPage = false;
        JSONObject rawJsonObj = new JSONObject(mailListPage);
        int totalCount;
        try {
            totalCount = rawJsonObj.getInt("mailCount");
        } catch (JsonException e) {
            //如果没有获取到总邮件数,默认为最后一页
            return true;
        }
        //总页数,0:第一页,1:第二页
        int pageTotal = totalCount / 20;
        if (totalCount % 20 != 0) {
            pageTotal++;
        }
        //如果当前页数大于等于总页数,当前页为最后一页
        if (currentPageIndex >= pageTotal) {
            isLastPage = true;
        }

        return isLastPage;
    }

    /**
     * 解析邮件列表
     *
     * @param mailListPage 邮件列表页面
     * @return 邮件列表
     */
    private List<Mail21cnEntity> parseMailListPage(String mailListPage) {

        List<Mail21cnEntity> mail21cnEntityList = new ArrayList<>();

        JSONArray jsonArray = new JSONObject(mailListPage).getJSONArray("mailList");
        for (int i = 0; i < jsonArray.length(); i++) {
            JSONObject mailJsonObj = null;
            try {
                mailJsonObj = jsonArray.getJSONObject(i);
                Mail21cnEntity mail21cnEntity = parseMailString(mailJsonObj);
                mail21cnEntityList.add(mail21cnEntity);
            } catch (ParseMailException e) {
                String errMsg = e.getMessage();
                if (errMsg.equals(StatusCode.MAIL_PARSE_EXPIRE)) {
                    printlnInfo("半年内邮件解析完成");
                    break;
                } else {
                    printlnErr("解析邮件失败," + errMsg + ":" + mailJsonObj);
                }
            }
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        return mail21cnEntityList;
    }

    /**
     * 解析邮件字符串,并获取邮件内容
     *
     * @param mailJsonObj 邮件json对象
     * @return 邮件实体
     * @throws ParseMailException 解析失败
     */
    private Mail21cnEntity parseMailString(JSONObject mailJsonObj) throws ParseMailException {

        Date date = new Date();
        try {
            long mailMillis = mailJsonObj.getLong("sendDate");
            date = new Date(mailMillis);
        } catch (JSONException e) {
            printlnErr("未获取到发送时间戳,默认使用当前时间戳:" + e.getMessage());
        }
        //邮件日期是否在半年之内
        isDuringHalfYear = PublicMethod.isDuringHalfYear(date);
        if (!isDuringHalfYear) {
            throw new ParseMailException(StatusCode.MAIL_PARSE_EXPIRE);
        }
        String subject;
        try {
            subject = mailJsonObj.getString("subject");
        } catch (JSONException e) {
            throw new ParseMailException(StatusCode.MAIL_PARSE_NO_SUBJECT);
        }
        //如果主题中不包含账单关键字,抛出异常
        if (!subject.contains(StatusCode.MAIL_CREDIT_KEY_WORD)) {
            throw new ParseMailException(StatusCode.MAIL_PARSE_NOT_CREDIT);
        }
        //如果没有获取到邮件id,抛出异常
        String messageId;
        int msId;
        try {
            messageId = mailJsonObj.getString("messageId");
            msId = mailJsonObj.getInt("msId");
        } catch (JSONException e) {
            throw new ParseMailException(StatusCode.MAIL_PARSE_NO_ID);
        }
        if (messageId.isEmpty() || messageId.isBlank()) {
            throw new ParseMailException(StatusCode.MAIL_PARSE_NO_ID);
        }
        String sender = null;
        String sendAddr = null;
        String recipient = null;
        String recipientAddr = null;
        try {
            //发送人
            sender = mailJsonObj.getString("sender");
            //发送地址
            sendAddr = mailJsonObj.getJSONArray("sendersAddress").getString(0);
            //收件人
            recipient = mailJsonObj.getString("receiver");
            //收件地址
            recipientAddr = mailJsonObj.getJSONArray("toAddress").getString(0);
        } catch (JSONException ignore) {
        }
        //邮件详情
        String content;
        try {
            content = getMailContent(messageId, msId);
        } catch (JSONException e) {
            throw new ParseMailException(StatusCode.MAIL_PARSE_NO_CONTENT);
        }

        return new Mail21cnEntity(messageId, subject, sender, sendAddr, recipient, recipientAddr, date, content);
    }

    /**
     * 根据邮件id,获取邮件详情
     *
     * @param messageId 邮件id
     * @return 邮件详情
     * @throws MailDetailException 未获取到邮件内容
     */
    private String getMailContent(String messageId, int msId) throws MailDetailException {
        String[] headers = {
                "Accept", "application/json, text/javascript, */*; q=0.01",
                "Content-Type", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8"
        };
        String detail;
        try {
            String uri = "http://mail.21cn.com/w2/mail/getMailDisplayMessage.do?" +
                    "messageId=" + messageId +
                    "&msId=" + msId +
                    "&labelId=1";
            String body = PublicMethod.getResponseElement(httpClient, requestBuilder, uri, ResponseElement.BODY, headers);
            detail = RegexSearcher.matchFirst(body, "(?<=<div id=\"mail-content\">[\\s]{0,100})<html>((?!</html>)[\\s\\S])*</html>");
        } catch (IOException | InterruptedException e) {
            throw new MailDetailException(StatusCode.MAIL_PARSE_NO_CONTENT);
        }
        return detail;
    }
}
